/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ola_cloud.h"
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include "ola_log.h"
#include "ola_coap_net.h"
#include "ola_error.h"
#include "ola_auth.h"
#include "ola_register.h"
#include "ola_login.h"
#include "ola_sdk.h"
#include "ola_random.h"
#include "ola_pal_network.h"
#include "ola_coap_net.h"
#include "ola_def.h"
#include "ola_link.h"
#include "securec.h"
#include "ola_data.h"
#ifdef OLA_DEMO_TEST
#include "ola_service.h"
#include "ola_base_utils.h"
#endif

#define LOGIN_ANTI_SURGE_INIT_MAX_TIME  (1000 * 5)
#define LOGIN_ANTI_SURGE_MAX_COUNT      5

typedef enum {
    CONNECT_CLOUT_STATE_INIT = 0,
    CONNECT_CLOUT_STATE_CREATE_CONTEXT,
    CONNECT_CLOUT_STATE_DEV_AUTH,
    CONNECT_CLOUT_STATE_REGISTER,
    CONNECT_CLOUT_STATE_LOGIN,
    CONNECT_CLOUT_STATE_ANTI_SURGE,
    CONNECT_CLOUT_STATE_FINISH,
    CONNECT_CLOUT_STATE_FAILED,
} ConnectCloudState;


typedef struct {
    bool flag;
    unsigned long long nextLoginTime;
    unsigned long long minTime;
    unsigned long long maxTime;
    unsigned char loginCount;
} LoginAntiSurgeParam;

static ConnectCloudState g_connectState = CONNECT_CLOUT_STATE_INIT;
/* 连云防浪涌参数 */
static LoginAntiSurgeParam g_antiSurge;

static int AntiSurgeParamInit(bool isNeed)
{
    if (isNeed) {
        g_antiSurge.flag = true;
        g_antiSurge.loginCount = 0;
        if (OlaGetTime(&g_antiSurge.nextLoginTime) != 0) {
            OlaLogError("get time error");
            return OLA_OS_ERR_GET_TIME;
        }
        g_antiSurge.minTime = 0;
        g_antiSurge.maxTime = LOGIN_ANTI_SURGE_INIT_MAX_TIME;
        OlaLogDebug("anti suger start, curtime[%llu]", g_antiSurge.nextLoginTime);
    } else {
        g_antiSurge.flag = false;
    }
    return OLA_OK;
}

static bool GetAntiSurgeFlag(void)
{
    return g_antiSurge.flag;
}

/*
 * 触发登录防浪涌后，每次登录失败延时时间范围
 * 第1次: 5s - 15s
 * 第2次: 15s - 45s
 * 第3次: 45s - 2m15s
 * 第4次: 2m15s - 6m45s
 * 第5次: 6m45s - 20m15s
 */
static int AntiSurgeParamRefresh(void)
{
    ++g_antiSurge.loginCount;
    if (g_antiSurge.loginCount > LOGIN_ANTI_SURGE_MAX_COUNT) {
        OlaLogWarning("anti surge over max times");
        return OLA_CONNECT_ERR_LOGIN_ANTI_SURGE_OVER_TIME;
    }
    g_antiSurge.minTime = g_antiSurge.maxTime;
    /* 最大值为 LOGIN_ANTI_SURGE_INIT_MAX_TIME * 3^LOGIN_ANTI_SURGE_MAX_COUNT，不会溢出 */
    g_antiSurge.maxTime = g_antiSurge.maxTime * 3;

    unsigned long long random = 0;
    int ret = OlaRand((unsigned char *)&random, sizeof(random));
    if (ret != OLA_OK) {
        OlaLogError("get random error");
        return ret;
    }
    if (OlaGetTime(&g_antiSurge.nextLoginTime) != 0) {
        OlaLogError("get time error");
        return OLA_OS_ERR_GET_TIME;
    }
    unsigned long long delta = g_antiSurge.maxTime - g_antiSurge.minTime;
    unsigned long long delayTime = random % delta + g_antiSurge.minTime;
    if (g_antiSurge.nextLoginTime > UINT64_MAX - delayTime) {
        OlaLogError("time exceed except");
        return OLA_OS_ERR_GET_TIME;
    }
    g_antiSurge.nextLoginTime += delayTime;
    OlaLogDebug("login anti surge, count[%u] next[%llu]",
        g_antiSurge.loginCount, g_antiSurge.nextLoginTime);
    return OLA_OK;
}

static int IsAntiSurgeTime(bool *state)
{
    unsigned long long curTime;
    if (OlaGetTime(&curTime) != 0) {
        OlaLogError("get time error");
        return OLA_OS_ERR_GET_TIME;
    }
    *state = g_antiSurge.nextLoginTime > curTime;
    return OLA_OK;
}

static int AntiSurgeProcess(void)
{
    /* 防浪涌期间执行coap io，用于监控链路状态 */
    int ret = OlaCoapProcess(0);
    if (ret != OLA_OK) {
        OlaLogWarning("coap process error");
        return ret;
    }
    bool isAntiSurgeTime = false;
    ret = IsAntiSurgeTime(&isAntiSurgeTime);
    if (ret != OLA_OK) {
        OlaLogWarning("check anti surge time error");
        return ret;
    }

    if (isAntiSurgeTime) {
        OlaSleepMs(OLA_MS_PER_SECOND);
        return OLA_STATE_RUNING;
    }
    return OLA_OK;
}

static void SetConnectState(ConnectCloudState state)
{
    g_connectState = state;
}

static ConnectCloudState GetConnectState(void)
{
    return g_connectState;
}

static void ConnectCloudEventHandler(OlaCoapContext *context, OlaCoapEvent event)
{
    OlaLinkParam *linkParam = OlaGetLinkParam(OLA_LINK_MODE_CLOUD);
    if ((linkParam == NULL) || (linkParam->ctx == NULL)) {
        OlaLogWarning("cloud link param is null");
        return;
    }
    if (context != linkParam->ctx) {
        OlaLogWarning("unknow context");
        return;
    }
    switch (event) {
        case OLA_COAP_EVENT_SOCKET_SELECT_ERROR:
        case OLA_COAP_EVENT_SOCKET_READ_ERROR:
        case OLA_COAP_EVENT_SOCKET_WRITE_ERROR:
            OlaLogWarning("coap context error");
            SetConnectState(CONNECT_CLOUT_STATE_FAILED);
            break;
        default:
            break;
    }
}

static int GetCloudUrlAndProt(const OlaData *url, char *buf, unsigned int bufLen, unsigned short *port)
{
    /* 根据冒号分割url与端口号 */
    const char *p = strchr((const char *)url->data, ':');
    unsigned int srcLen;
    if (p != NULL) {
        srcLen = p - (const char *)url->data;
        ++p;
        int tempPort = atoi(p);
        if ((tempPort < 0) || (tempPort > UINT16_MAX)) {
            OlaLogWarning("invalid port or url");
            return OLA_CONNECT_ERR_PORT_INVALID;
        }
        *port = tempPort;
    } else {
        srcLen = url->len;
    }
    if (srcLen > bufLen) {
        OlaLogWarning("url too long[%u]", srcLen);
        return OLA_CONNECT_ERR_URL_INVALID;
    }
    if (strncpy_s(buf, bufLen, (const char *)url->data, srcLen) != EOK) {
        OlaLogError("strncpy error");
        return OLA_SECUREC_ERR_STRNCPY;
    }
    return OLA_OK;
}

static int ConnectCloudCreateContext(void)
{
    OlaData url = {NULL, 0};
    int ret = OlaGetData(OLA_DATA_TYPE_CLOUD_URL, &url);
    if (ret != OLA_OK) {
        OlaLogWarning("get url error");
        return ret;
    }
    if ((url.data == NULL) || (url.len == 0) || (strlen((const char *)url.data) != url.len)) {
        OlaLogWarning("url invalid");
        return OLA_CONNECT_ERR_URL_EMPTY;
    }

    unsigned short cloudPort = CLOUD_COAP_PORT;
    char cloudUrl[OLA_DEV_CLOUD_URL_LEN + 1] = {0};
    if (GetCloudUrlAndProt(&url, cloudUrl, sizeof(cloudUrl), &cloudPort) != OLA_OK) {
        OlaLogWarning("url invalid");
        return OLA_CONNECT_ERR_URL_INVALID;
    }

    OlaCoapContext *ctx = OlaCoapNewContext(cloudUrl, cloudPort, OLA_COAP_PROTOCOL_TYPE_TLS);
    if (ctx == NULL) {
        OlaLogWarning("create context error");
        return OLA_CONNECT_ERR_NEW_CONTEXT;
    }

    ret = OlaCoapRegisterEventHandler(ctx, ConnectCloudEventHandler);
    if (ret != OLA_OK) {
        OlaLogWarning("register event handler error");
        ret = OLA_CONNECT_ERR_REGISTER_CONTEXT_HANDLER;
        goto FAILED;
    }
    OlaLinkParam *linkParam = OlaGetLinkParam(OLA_LINK_MODE_CLOUD);
    if (linkParam == NULL) {
        OlaLogWarning("cloud link param is null");
        ret = OLA_LINK_ERR_CONTEXT_NULL;
        goto FAILED;
    }
    linkParam->ctx = ctx;
    linkParam->useFlag = true;

    return OLA_OK;
FAILED:
    (void)OlaCoapFreeContext(ctx);
    return ret;
}

static int ConnectCloudInit(void)
{
    OlaLinkParam *linkParam = OlaGetLinkParam(OLA_LINK_MODE_CLOUD);
    if (linkParam == NULL) {
        OlaLogWarning("cloud link param is null");
        return OLA_LINK_ERR_CONTEXT_NULL;
    }
    (void)memset_s(linkParam, sizeof(OlaLinkParam), 0, sizeof(OlaLinkParam));

    int ret;
    (void)memset_s(&g_antiSurge, sizeof(LoginAntiSurgeParam), 0, sizeof(LoginAntiSurgeParam));
    /* 已注册设备登录启动防浪涌 */
    ret = AntiSurgeParamInit(OlaIsRegister());
    if (ret != OLA_OK) {
        OlaLogWarning("init anti surge param error");
        return ret;
    }

    return OLA_OK;
}

static void ConnectCloudOver(void)
{
    (void)memset_s(&g_antiSurge, sizeof(LoginAntiSurgeParam), 0, sizeof(LoginAntiSurgeParam));
}

static void ConnectCloudFailed(void)
{
    ConnectCloudOver();
    OlaLinkParam *linkParam = OlaGetLinkParam(OLA_LINK_MODE_CLOUD);
    if (linkParam == NULL) {
        OlaLogWarning("cloud link param is null");
        return;
    }
    if (linkParam->ctx != NULL) {
        (void)OlaCoapFreeContext(linkParam->ctx);
    }
}

static void JumpToTargetState(ConnectCloudState state)
{
    switch (state) {
        case CONNECT_CLOUT_STATE_DEV_AUTH:
            OlaDeviceAuthInit();
            break;
        case CONNECT_CLOUT_STATE_REGISTER:
            OlaRegisterInit();
            break;
        case CONNECT_CLOUT_STATE_LOGIN:
            OlaLoginInit();
        default:
            break;
    }
    OlaLogNotice("set connect state [%d]", state);
    SetConnectState(state);
}

static void HandleCloudInitReturn(int errcode)
{
    if (errcode != OLA_OK) {
        OlaLogWarning("connect cloud init error");
        JumpToTargetState(CONNECT_CLOUT_STATE_FAILED);
        return;
    }
    JumpToTargetState(CONNECT_CLOUT_STATE_CREATE_CONTEXT);
}

static void HandleCreateContextReturn(int errcode)
{
    if (errcode != OLA_OK) {
        OlaLogWarning("create context error");
        JumpToTargetState(CONNECT_CLOUT_STATE_FAILED);
        return;
    }
    /* 判断是否需要认证 */
    bool isDevAuth = OlaIsDevAuth();
    if (!isDevAuth) {
        JumpToTargetState(CONNECT_CLOUT_STATE_DEV_AUTH);
        return;
    }
    /* 判断是否需要注册 */
    bool isRegister = OlaIsRegister();
    if (!isRegister) {
        JumpToTargetState(CONNECT_CLOUT_STATE_REGISTER);
        return;
    }
    JumpToTargetState(CONNECT_CLOUT_STATE_LOGIN);
}

static void HandleDevAuthReturn(int errcode)
{
    if (errcode == OLA_STATE_RUNING) {
        return;
    }
    if (errcode != OLA_OK) {
        OlaLogWarning("dev auth error");
        JumpToTargetState(CONNECT_CLOUT_STATE_FAILED);
        return;
    }
    JumpToTargetState(CONNECT_CLOUT_STATE_REGISTER);
}

static void HandleRegisterReturn(int errcode)
{
    if (errcode == OLA_STATE_RUNING) {
        return;
    }
    if (errcode != OLA_OK) {
        OlaLogWarning("register error");
        JumpToTargetState(CONNECT_CLOUT_STATE_FAILED);
        return;
    }
    JumpToTargetState(CONNECT_CLOUT_STATE_LOGIN);
}

static void HandleLoginReturn(int errcode)
{
    if (errcode == OLA_STATE_RUNING) {
        return;
    }
    if (errcode != OLA_OK) {
        if (GetAntiSurgeFlag() && (AntiSurgeParamRefresh() == OLA_OK)) {
            JumpToTargetState(CONNECT_CLOUT_STATE_ANTI_SURGE);
            return;
        }
        OlaLogWarning("register error");
        JumpToTargetState(CONNECT_CLOUT_STATE_FAILED);
        return;
    }
    JumpToTargetState(CONNECT_CLOUT_STATE_FINISH);
}

static void HandleAntiSurgeReturn(int errcode)
{
    if (errcode == OLA_STATE_RUNING) {
        return;
    }
    if (errcode != OLA_OK) {
        OlaLogWarning("anti surge error");
        JumpToTargetState(CONNECT_CLOUT_STATE_FAILED);
        return;
    }
    JumpToTargetState(CONNECT_CLOUT_STATE_LOGIN);
}

#ifdef OLA_DEMO_TEST
/* 打桩设备属性上报函数，上报固定数据 */
static void DeviceNotify(void)
{
    int ret;
    /* 组装数据 */
    OlaData payload = {NULL, 0};
    ret = OlaQueryDeviceService(NULL, &payload);
    if (ret != OLA_OK) {
        OlaLogError("query error:%d", ret);
        return;
    }
    /* 上报数据 */
    ret = OlaReportDeviceProperties(&payload, NULL);
    if (ret != OLA_OK) {
        OlaLogEmerg("DeviceNotify result:%d\r", ret);
    }
    OlaDataFree(&payload);
    return;
}
#endif

/* 返回OLA_STATE_RUNING持续运行 */
static int ConnectCloudStateMachine(void)
{
    int ret = OLA_STATE_RUNING;
    int errcode;
    switch (GetConnectState()) {
        case CONNECT_CLOUT_STATE_INIT:
            errcode = ConnectCloudInit();
            HandleCloudInitReturn(errcode);
            break;
        case CONNECT_CLOUT_STATE_CREATE_CONTEXT:
            errcode = ConnectCloudCreateContext();
            HandleCreateContextReturn(errcode);
            break;
        case CONNECT_CLOUT_STATE_DEV_AUTH:
            errcode = OlaDeviceAuthProcess();
            HandleDevAuthReturn(errcode);
            break;
        case CONNECT_CLOUT_STATE_REGISTER:
            errcode = OlaRegisterProcess();
            HandleRegisterReturn(errcode);
            break;
        case CONNECT_CLOUT_STATE_LOGIN:
            errcode = OlaLoginProcess();
            HandleLoginReturn(errcode);
            break;
        case CONNECT_CLOUT_STATE_ANTI_SURGE:
            errcode = AntiSurgeProcess();
            HandleAntiSurgeReturn(errcode);
            break;
        case CONNECT_CLOUT_STATE_FINISH:
#ifdef OLA_DEMO_TEST
            /* 打桩代码，登录完成后上报设备属性数据 */
            DeviceNotify();
#endif
            ConnectCloudOver();
            ret = OLA_OK;
            break;
        case CONNECT_CLOUT_STATE_FAILED:
            ConnectCloudFailed();
            ret = OLA_ERROR;
            break;
        default:
            OlaLogWarning("invalid connect cloud state");
            ret = OLA_ERROR;
            break;
    }
    return ret;
}

int OlaConnectCloudProcess(void)
{
    SetConnectState(CONNECT_CLOUT_STATE_INIT);
    int ret = OLA_OK;
    int netState;
    while (true) {
        /* 网络连接检查 */
        ret = OlaGetNetworkState(&netState);
        if ((ret != 0) || (netState != 1)) {
            OlaLogError("network error");
            OlaConnectCloudExit();
        }
        ret = ConnectCloudStateMachine();
        if (ret == OLA_OK) {
            break;
        } else if (ret == OLA_STATE_RUNING) {
            continue;
        } else {
            break;
        }
    }
    return ret;
}

void OlaConnectCloudExit(void)
{
    SetConnectState(CONNECT_CLOUT_STATE_FAILED);
}
